package spacewar2.clients;

import java.awt.Color;
import java.util.ArrayList;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.UUID;

import spacewar2.actions.SpacewarAction;
import spacewar2.actions.SpacewarPurchaseEnum;
import spacewar2.objects.Ship;
import spacewar2.objects.SpacewarActionableObject;
import spacewar2.objects.SpacewarObject;
import spacewar2.powerups.SpacewarPowerup;
import spacewar2.shadows.Shadow;
import spacewar2.simulator.Toroidal2DPhysics;
import spacewar2.utilities.Position;
import spacewar2.utilities.Vector2D;

/**
 * All clients must inherit from the abstract client
 * 
 * @author amy
 */
abstract public class TeamClient {
	/**
	 * The team color (used by the GUI)
	 */
	Color teamColor;
	
	/**
	 * The name of the team
	 */
	String teamName;
	
	/**
	 * Optional:  if the agent needs to read in from a file, it can specify the path to it here 
	 */
	String knowledgeFile;
	
	/**
	 * Save the random number generator for future use
	 */
	Random random;
	
	public void setRandom(Random random) {
		this.random = random;
	}

	/**
	 * @return the teamColor
	 */
	public Color getTeamColor() {
		return teamColor;
	}

	/**
	 * @param teamColor the teamColor to set
	 */
	public void setTeamColor(Color teamColor) {
		this.teamColor = teamColor;
	}

	/**
	 * @return the teamName
	 */
	public String getTeamName() {
		return teamName;
	}

	/**
	 * @param teamName the teamName to set
	 */
	public void setTeamName(String teamName) {
		this.teamName = teamName;
	}
	
	/**
	 * Called when the team is created from the config file
	 * @param knowledgeFile
	 */
	public void setKnowledgeFile(String knowledgeFile) {
		this.knowledgeFile = knowledgeFile;
	}
	
	/**
	 * Called before an action begins.  Return a hash map of ids to SpacewarActions  
	 * 
	 * @param space physics
	 * @param actionableObjects the ships and bases for this team
	 * @param random random number generator
	 * @return
	 */
	abstract public Map<UUID, SpacewarAction> getMovementStart(Toroidal2DPhysics space, Set<SpacewarActionableObject> actionableObjects);
	
	/**
	 * Called when actions end but before time advances.  Can be used to see
	 * if a ship died or other useful physics checks.
	 * 
	 * @param space
	 * @param actionableObjects the ships and bases for this team
	 * @param ships
	 */
	abstract public void getMovementEnd(Toroidal2DPhysics space, Set<SpacewarActionableObject> actionableObjects);
	
	/**
	 * Called each time step to get the power up or weapon for each actionable object 
	 * 
	 * @param space physics
	 * @param actionableObjects the ships and bases for this team
	 * @param random random number generator
	 * @return
	 */
	abstract public Map<UUID, SpacewarPowerup> getPowerups(Toroidal2DPhysics space, Set<SpacewarActionableObject> actionableObjects);

	
	/**
	 * Called once per turn to see if the team wants to purchase anything with its
	 * existing money.  Can only purchase one item per turn.
	 * 
	 * @param space
	 * @param ships
	 * @param money how much money you have
	 * @param currentCostOfBase how much a base currently costs for this team
	 * @return
	 */
	abstract public Map<UUID, SpacewarPurchaseEnum> getTeamPurchases(Toroidal2DPhysics space, Set<Ship> ships, int availableMoney, int currentCostOfBase);
	
	/**
	 * Called when the client is created
	 */
	abstract public void initialize();
	
	/**
	 * Called when the client is shut down
	 */
	abstract public void shutDown();

	/**
	 * Return any new shadows that the team client wants to draw
	 * @return an array list of objects that extend the Shadow class
	 */
	abstract public Set<Shadow> getNewShadows();

	/**
	 * Return any old shadows that the team client wants to remove from being drawn
	 * @return an array list of objects that extend the Shadow class
	 */
	abstract public Set<Shadow> getOldShadows();
	
	
	/**
	 * Check to see if following a straight line path between two given locations would result in a collision with a provided set of obstructions
	 * 
	 * @author Andrew and Thibault
	 * 
	 * @param  startPosition the starting location of the straight line path
	 * @param  goalPosition the ending location of the straight line path
	 * @param  obstructions an ArrayList of SpacewarObject obstructions (i.e., if you don't wish to consider mineable asteroids or beacons obstructions)
	 * @param  space the simulated game space
	 * @param  myAObj the object following the path (used to determine free space buffer size - this could perhaps be changed to SpacewarObjects instead (e.g., if determining moving asteroid paths)
	 * @return Whether or not a straight line path between two positions contains obstructions from a given set
	 */
	public boolean isPathClearOfObstructions(Position startPosition, Position goalPosition, ArrayList<SpacewarObject> obstructions, Toroidal2DPhysics space, SpacewarActionableObject myAObj) {
		Vector2D pathToGoal = space.findShortestDistanceVector(startPosition,  goalPosition); 	// Shortest straight line path from startPosition to goalPosition
		double distanceToGoal = pathToGoal.getMagnitude();										// Distance of straight line path

		boolean pathIsClear = true; // Boolean showing whether or not the path is clear
		
		// Calculate distance between obstruction center and path (including buffer for ship movement)
		// Uses hypotenuse * sin(theta) = opposite (on a right hand triangle)
		Vector2D pathToObstruction; // Vector from start position to obstruction
		double angleBetween; 		// Angle between vector from start position to obstruction
		
		// Loop through obstructions
		for (SpacewarObject obstruction: obstructions) {
			// If the distance to the obstruction is greater than the distance to the end goal, ignore the obstruction
			pathToObstruction = space.findShortestDistanceVector(startPosition, obstruction.getPosition());
		    if (pathToObstruction.getMagnitude() > distanceToGoal) {
				continue;
			}
		    
			// Ignore angles > 90 degrees
			angleBetween = Math.abs(pathToObstruction.angleBetween(pathToGoal));
			if (angleBetween > Math.PI/2) {
				continue;
			}

			// Compare distance between obstruction and path with buffer distance
			if (pathToObstruction.getMagnitude() * Math.sin(angleBetween) < obstruction.getRadius() + myAObj.getRadius()*1.5) {
				pathIsClear = false;
				break;
			}
		}
		
		return pathIsClear;
		
	}

	
}
